package org.springframework.boot.autoconfigure.data.redis;

import java.time.Duration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;


import cn.hutool.core.util.StrUtil;
import io.lettuce.core.ReadFrom;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import lombok.extern.slf4j.Slf4j;

import static org.apache.commons.pool2.impl.BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS;


@Configuration
@ConditionalOnClass(RedisClient.class)
@ConfigurationProperties(prefix = "spring.redis.lettuce.pool")
@Slf4j
public class LettuceConfig extends RedisConnectionConfiguration {

    // 连接池最大连接数
    private int maxActive = 100;
    // 连接池中允许的最小空闲连接数
    private int minIdle = 10;
    // 连接池中允许的最大空闲连接数
    private int maxIdle = 50;
    // 获取连接时的最大等待毫秒数(如果设置为阻塞时使用)
    private String maxWait = "PT1M";
    // 启用连接校验
    private boolean testOnBorrow = true;
    // 归还连接时校验连接是否有效
    private boolean testOnReturn = false;
    // 在空闲时定期检测连接有效性
    private boolean testWhileIdle = true;
    // 连接空闲多久后会被销毁，单位毫秒
    private String minEvictableIdleTime = "PT5M";
    // 定期检测空闲连接，并驱逐，默认1分钟
    private String timeBetweenEvictionRuns = "PT1M";
    // 限制每次驱逐线程运行时扫描的连接数量
    private int numTestsPerEvictionRun = 50;
    // 定义连接池耗尽时的行为（阻塞等待 or 立即失败）
    private boolean blockWhenExhausted = true;

    // 命令执行超时时间
    @Value(value = "${spring.redis.timeout:PT30S}")
    private String commandTimeout;

    // 关闭Redis客户端前等待任务完成的超时时间，超时后强制终止
    @Value(value = "${spring.redis.lettuce.shutdown-timeout:PT30S}")
    private String shutdownTimeout;


    public int getMaxActive() {
        return maxActive;
    }

    public void setMaxActive(int maxActive) {
        this.maxActive = maxActive;
    }

    public int getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(int minIdle) {
        this.minIdle = minIdle;
    }

    public int getMaxIdle() {
        return maxIdle;
    }

    public void setMaxIdle(int maxIdle) {
        this.maxIdle = maxIdle;
    }

    public String getMaxWait() {
        return maxWait;
    }

    public void setMaxWait(String maxWait) {
        this.maxWait = maxWait;
    }

    public boolean isTestOnBorrow() {
        return testOnBorrow;
    }

    public void setTestOnBorrow(boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }

    public boolean isTestOnReturn() {
        return testOnReturn;
    }

    public void setTestOnReturn(boolean testOnReturn) {
        this.testOnReturn = testOnReturn;
    }

    public boolean isTestWhileIdle() {
        return testWhileIdle;
    }

    public void setTestWhileIdle(boolean testWhileIdle) {
        this.testWhileIdle = testWhileIdle;
    }

    public String getMinEvictableIdleTime() {
        return minEvictableIdleTime;
    }

    public void setMinEvictableIdleTime(String minEvictableIdleTime) {
        this.minEvictableIdleTime = minEvictableIdleTime;
    }

    public String getShutdownTimeout() {
        return shutdownTimeout;
    }

    public String getTimeBetweenEvictionRuns() {
        return timeBetweenEvictionRuns;
    }

    public void setTimeBetweenEvictionRuns(String timeBetweenEvictionRuns) {
        this.timeBetweenEvictionRuns = timeBetweenEvictionRuns;
    }

    public int getNumTestsPerEvictionRun() {
        return numTestsPerEvictionRun;
    }

    public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
        this.numTestsPerEvictionRun = numTestsPerEvictionRun;
    }

    public boolean isBlockWhenExhausted() {
        return blockWhenExhausted;
    }

    public void setBlockWhenExhausted(boolean blockWhenExhausted) {
        this.blockWhenExhausted = blockWhenExhausted;
    }

    public String getCommandTimeout() {
        return commandTimeout;
    }

    public void setCommandTimeout(String commandTimeout) {
        this.commandTimeout = commandTimeout;
    }

    public void setShutdownTimeout(String shutdownTimeout) {
        this.shutdownTimeout = shutdownTimeout;
    }

    protected LettuceConfig(RedisProperties properties, ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider, ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
        super(properties, sentinelConfigurationProvider, clusterConfigurationProvider);
    }

    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean(ClientResources.class)
    DefaultClientResources lettuceClientResources() {
        return DefaultClientResources.create();
    }

    @Bean
    public GenericObjectPoolConfig<StatefulRedisConnection<String, String>> poolConfiguration() {
        GenericObjectPoolConfig<StatefulRedisConnection<String, String>> poolConfig = new GenericObjectPoolConfig<>();
        poolConfig.setMaxTotal(maxActive);
        poolConfig.setMinIdle(minIdle);
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMaxWait(getDuration(maxWait));
        poolConfig.setTestOnBorrow(testOnBorrow);
        poolConfig.setTestOnReturn(testOnReturn);
        poolConfig.setTestWhileIdle(testWhileIdle);
        poolConfig.setMinEvictableIdleTime(getDuration(minEvictableIdleTime));
        poolConfig.setTimeBetweenEvictionRuns(getDuration(timeBetweenEvictionRuns));
        poolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
        poolConfig.setBlockWhenExhausted(blockWhenExhausted);
        return poolConfig;
    }

    private Duration getDuration(String timeStr) {
        if (StrUtil.isEmpty(timeStr) || StrUtil.startWith(timeStr,"-1")) {
            return Duration.ofMillis(DEFAULT_MAX_WAIT_MILLIS);
        }
        if (StrUtil.startWith(timeStr,"PT")) {
            return Duration.parse(timeStr);
        }
        try {
            return parse(timeStr);
        }catch (Exception e) {
            log.error("parse timeStr error, timeStr:{}", timeStr, e);
            return Duration.ZERO;
        }
    }

    @Bean
    public RedisConnectionFactory redisConnectionFactory(GenericObjectPoolConfig<StatefulRedisConnection<String, String>> poolConfiguration) {
        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder lettucePoolingClientConfigurationBuilder = LettucePoolingClientConfiguration
                .builder().readFrom(ReadFrom.MASTER)
                .poolConfig(poolConfiguration);
        if (StrUtil.isNotEmpty(commandTimeout)) {
            lettucePoolingClientConfigurationBuilder.commandTimeout(getDuration(commandTimeout));
        }
        if (StrUtil.isNotEmpty(shutdownTimeout)) {
            lettucePoolingClientConfigurationBuilder.shutdownTimeout(getDuration(shutdownTimeout));
        }
        LettucePoolingClientConfiguration clientConfig = lettucePoolingClientConfigurationBuilder.build();
        return createLettuceConnectionFactory(clientConfig);
    }

    private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
        if (getSentinelConfig() != null) {
            return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
        }
        if (getClusterConfiguration() != null) {
            return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
        }
        return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
    }

    private static final Pattern PATTERN = Pattern.compile("([0-9]+)([smhd])");

    public static Duration parse(String timeStr) {
        long totalSeconds = 0;
        Matcher matcher = PATTERN.matcher(timeStr);

        while (matcher.find()) {
            long value = Long.parseLong(matcher.group(1));
            String unit = matcher.group(2).toLowerCase();

            switch (unit) {
                case "s":
                    totalSeconds += value;
                    break;
                case "m":
                    totalSeconds += value * 60;
                    break;
                case "h":
                    totalSeconds += value * 3600;
                    break;
                default:
                    throw new IllegalArgumentException("Invalid unit: " + unit);
            }
        }

        return Duration.ofSeconds(totalSeconds);
    }


}
